/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 adventure_sounds.cpp

	$Header: /heroes4/adventure_sounds.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include <algorithm>
#include <strstream>
#include <vector>
#include <map>
#include "adv_object_type.h"
#include "adv_object_type_properties.h"
#include "adventure_map.h"
#include "adventure_sounds.h"
#include "enum_operations.h"
#include "qualified_adv_object_type.h"
#include "music.h"
#include "owned_ptr.h"
#include "playing_sound.h"
#include "playing_sound_ptr.h"
#include "sound.h"
#include "table.h"
#include "table_ptr.h"
#include "windows_game.h"

const int k_almost_min_volume = -9990;	// close to min Defined by DirectSound, but avoids termination of sound buffer
 

namespace
{
	const int k_max_sound_radius = 12;
	const int k_max_sound_dist_sq = k_max_sound_radius * k_max_sound_radius;

	std::string adventure_sounds[] =
	{
		std::string( "miscellaneous.town_build" ),
		std::string( "miscellaneous.dig" ),
		std::string( "miscellaneous.flag_mine" ),
		std::string( "miscellaneous.pick_up.01" ),
		std::string( "miscellaneous.pick_up.02" ),
		std::string( "miscellaneous.pick_up.03" ),
		std::string( "miscellaneous.pick_up.04" ),
		std::string( "miscellaneous.pick_up.05" ),
		std::string( "miscellaneous.pick_up.06" ),
		std::string( "miscellaneous.teleport_in" ),
		std::string( "miscellaneous.teleport_out" )
	};

	std::string dialog_sounds[] =
	{
		std::string( "dialogue.abandoned_mine" ),
		std::string( "dialogue.artifact" ),
		std::string( "dialogue.creature_bank" ),
		std::string( "dialogue.experience" ),
		std::string( "dialogue.kingdom_overview" ),
		std::string( "dialogue.level_up" ),
		std::string( "dialogue.luck" ),
		std::string( "dialogue.magi " ),
		std::string( "dialogue.mana" ),
		std::string( "dialogue.marketplace" ),
		std::string( "dialogue.military" ),
		std::string( "dialogue.morale" ),
		std::string( "dialogue.new_class" ),
		std::string( "dialogue.new_month" ),
		std::string( "dialogue.portal" ),
		std::string( "dialogue.power_up" ),
		std::string( "dialogue.puzzle" ),
		std::string( "dialogue.quest" ),
		std::string( "dialogue.recruit" ),
		std::string( "dialogue.sanctuary" ),
		std::string( "dialogue.shipyard" ),
		std::string( "dialogue.shrine" ),
		std::string( "dialogue.sign" ),
		std::string( "dialogue.stables" ),
		std::string( "dialogue.tavern" ),
		std::string( "dialogue.thieves_guild" ),
		std::string( "dialogue.tower" ),
		std::string( "dialogue.treasure" ),
		std::string( "dialogue.treasure_chest" ),
		std::string( "dialogue.university" ),
		std::string( "dialogue.view_world" ),
		std::string( "miscellaneous.button" ),
		std::string( "combat.start" )
	};

	// active sounds queue
	typedef std::vector<t_managed_sound_ptr> t_active_sounds;

	t_active_sounds active_sounds;

	// ---------------------------------------------
	// list of sounds for an adventure object
	// ---------------------------------------------
	class t_major_type_sounds
	{
	public:
		t_sound_ptr get_major_sound(	t_qualified_adv_object_type const&	type,
										t_sound_cache&						cache,
										t_sound_cache const&					general_sound );
		t_sound_ptr get_sound(	t_qualified_adv_object_type const& type, 
								t_adv_object_type_properties const& properties,
								t_sound_cache const& general_sound );

	protected:
		t_sound_cache						m_major_sound;
		std::vector<t_sound_cache>			m_minor_type_sounds;
	};

	// ---------------------------------------------
	// adventure object sound containing major/minor sound types
	// ---------------------------------------------
	class t_adv_object_sound
	{
	public:
		t_sound_ptr get_sound( t_qualified_adv_object_type const& type );

	protected:
		t_sound_cache						m_general_sound;
		std::vector<t_major_type_sounds>	m_major_type_sounds;
	};

	// ---------------------------------------------
	// list of sounds for all adventure objects
	// ---------------------------------------------
	class t_adv_object_sounds
	{
	public:
		t_adv_object_sounds();
		t_sound_ptr get_sound( t_qualified_adv_object_type const& type );

	protected:
		std::vector<t_adv_object_sound>		m_sounds;
	};

	// ---------------------------------------------
	// go through map tiles near to the point and 
	// play sounds for them
	// ---------------------------------------------
	void play_sounds_for_nearby_adventure_objects( t_level_map_point_2d point, t_adventure_map const& map )
	{
		static const int k_half_distance = k_max_sound_radius / 2;

		// check the map tiles in radius around the point to see
		// if any objects are nearby that need to play sounds
		for( int row = 0; row < k_max_sound_radius; ++row )
		{
			for( int col = 0; col < k_max_sound_radius; ++col )
			{
				int row_delta = row - k_half_distance;
				int col_delta = col - k_half_distance;
				int map_row = point.row + row_delta;
				int map_col = point.column + col_delta;

				// we still check distance, because not all the tiles in the grid
				// will fall within the sound radius
				int dist_sq = ( row_delta * row_delta ) + ( col_delta * col_delta );

				if( dist_sq <= k_max_sound_dist_sq )
				{
					// get the adventure tile
					t_level_map_point_2d map_point( map_row, map_col, point.level );

					if( !map.is_valid( t_map_point_2d( map_point ) ) )
						continue;

					t_abstract_adventure_tile const & tile = map.get_tile( map_point );

					for( int i = 0; i < tile.get_intersecting_object_count(); ++i )
					{
						t_adventure_map_object_id id = tile.get_intersecting_object_id( i );
						if( id != k_invalid_adventure_map_object_id )
						{
							t_adventure_object const & adv_object = map.get_adv_object( id );
							t_stationary_adventure_object const * adv_object_ptr = dynamic_cast<t_stationary_adventure_object const *>(&adv_object);
							if( adv_object_ptr )
							{
								t_qualified_adv_object_type type = adv_object_ptr->get_type();
								t_sound_ptr sound = get_adventure_sound( type );
								if( sound )
									play_adventure_sound( adv_object, sound, map_point, -9999, false );
							}
						}
					}
				}			
			}
		}
	}

}; // unnamed namespace


// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// Get an adventure sound by type only
// --------------------------------------------------------

// --------------------------------------------------------
// get an sound from an adventure sound enum
// --------------------------------------------------------
t_sound_ptr get_adventure_sound( t_adventure_sound_type type )
{
	static bool          initialized = false;
	static t_sound_cache caches[k_adventure_sound_count];

	if (!initialized)
	{
		t_adventure_sound_type adventure_sound;

		for(	adventure_sound = t_adventure_sound_type(0); 
				adventure_sound < k_adventure_sound_count; 
				enum_incr( adventure_sound ) )
		{
			caches[adventure_sound] = t_sound_cache( adventure_sounds[adventure_sound] );
		}

		initialized = true;
	}

	t_sound_ptr result = caches[type].get();
 
	return result;
}

// ---------------------------------------------
// get sound associated with a dialog
// ---------------------------------------------
t_sound_ptr get_dialog_sound( t_dialog_sound_type type )
{
	static bool          initialized = false;
	static t_sound_cache caches[k_dialog_sound_count];

	if (!initialized)
	{
		t_dialog_sound_type dialog_sound;

		for(	dialog_sound = t_dialog_sound_type(0); 
				dialog_sound < k_dialog_sound_count; 
				enum_incr( dialog_sound ) )
		{
			caches[dialog_sound] = t_sound_cache( dialog_sounds[dialog_sound] );
		}

		initialized = true;
	}

	t_sound_ptr result = caches[type].get();
 
	return result;
}

// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// --------------------------------------------------------
// Get an adventure sound by type, major, and minor catagories
// --------------------------------------------------------

static std::string get_adv_keyword( t_qualified_adv_object_type const& type )
{
	std::string							result = "adv_object.";
	t_adv_object_type_properties const& properties = get_adv_object_type_properties( type );

	result += properties.get_keyword();

	return result;
}

static std::string get_major_keyword( t_qualified_adv_object_type const& type )
{
	std::string										result = get_adv_keyword( type );
	t_adv_object_type_properties const&				properties = get_adv_object_type_properties( type );
	t_adv_object_major_subtype_properties const&	major_properties = 
				properties.get_subtype_properties( type.major_subtype() );

	result += '.';
	result += major_properties.get_keyword();
	return result;
}


t_sound_ptr t_major_type_sounds::get_major_sound( t_qualified_adv_object_type const&	type,
												  t_sound_cache&						cache,
												  t_sound_cache const&					general_sound )
{
	// try to load the major sound
	if( m_major_sound.is_valid() )
	{
		cache = m_major_sound;
		return m_major_sound.get();
	}

	t_sound_ptr result;

	m_major_sound = t_sound_cache( get_major_keyword( type ) );
	result = m_major_sound.get();
	if (result.get() == 0)
	{
		m_major_sound = general_sound;
		result = general_sound.get();
	}	
	return result;
}

// ---------------------------------------------
// list of sounds for an adventure object
// ---------------------------------------------
t_sound_ptr t_major_type_sounds::get_sound(	t_qualified_adv_object_type const& type, 
											t_adv_object_type_properties const& properties,
											t_sound_cache const& general_sound )
{
	t_adv_object_major_subtype_properties const& major_properties = 
				properties.get_subtype_properties( type.major_subtype() );

	t_sound_cache result_cache;

	// see if we can load the major and minor type
	if ( major_properties.has_subtypes() )
	{
		if( m_minor_type_sounds.empty() )
			m_minor_type_sounds.resize( major_properties.get_subtype_count() );

		// try to get the sound using major.minor properties
		if( m_minor_type_sounds[type.minor_subtype()].is_valid() )
			return m_minor_type_sounds[type.minor_subtype()].get();
		
		t_adv_object_minor_subtype_properties const& minor_properties = 
			major_properties.get_subtype_properties( type.minor_subtype() );
		std::string name = get_major_keyword( type );

		name += '.';
		name += minor_properties.get_keyword();
		m_minor_type_sounds[type.minor_subtype()] = t_sound_cache( name );
		
		t_sound_ptr result = m_minor_type_sounds[type.minor_subtype()].get();

		if (result.get() != 0)
			return result;

		return get_major_sound( type, m_minor_type_sounds[type.minor_subtype()], general_sound );
	}

	t_sound_cache cache;

	return get_major_sound( type, cache, general_sound );
}

// ---------------------------------------------
// list of sounds for an adventure object
// ---------------------------------------------
t_sound_ptr t_adv_object_sound::get_sound( t_qualified_adv_object_type const& type )
{
	t_adv_object_type_properties const& properties = get_adv_object_type_properties( type );
	std::string name = "adv_object.";


	name += properties.get_keyword();

	// load the general sound
	if( !m_general_sound.is_valid() )
	{
		m_general_sound = t_sound_cache( name );
	}

	// see if we can get a sound with the major type
	if( properties.has_subtypes() )
	{
		if( m_major_type_sounds.empty() )
			m_major_type_sounds.resize( properties.get_subtype_count() );

		// try to get the sound
		t_sound_ptr sound_ptr =	m_major_type_sounds[type.major_subtype()].get_sound( type, properties, m_general_sound );
		if( sound_ptr.get() )
			return sound_ptr;
	}

	return m_general_sound.get();
}

// ---------------------------------------------
// list of sounds for all adventure objects
// ---------------------------------------------
t_adv_object_sounds::t_adv_object_sounds()
{
	m_sounds.resize( k_adv_object_type_count );
}

// ---------------------------------------------
// list of sounds for all adventure objects
// ---------------------------------------------
inline t_sound_ptr t_adv_object_sounds::get_sound(	t_qualified_adv_object_type const& type )
{
	t_adv_object_type object_type = type.get();

	return m_sounds[type].get_sound( type );
}


// ---------------------------------------------
// This is the function to call if you want to 
// load and adventure object sound from the
// resource file
// ---------------------------------------------
t_sound_ptr get_adventure_sound( t_qualified_adv_object_type const& type )
{
	static t_adv_object_sounds sounds;

	return sounds.get_sound( type );
}

// stop or start sounds when minimized
void handle_adventure_sound_minimization(bool minimized)
{
	static bool was_minimized = false;
	static bool music_is_saved = false;
	static int former_music_volume;
	static t_sound_cache saved_music;

	if (minimized == false && !was_minimized) // never have minimized yet
		return;

	// go through the current managed sound list and make sure a sound
	t_active_sounds::iterator iter;

	if (minimized)
		was_minimized = true;

	// handle music
	if (music_is_saved && !minimized) 
		play_music( saved_music );
	else if (minimized)
	{
		music_is_saved = true;
		saved_music = get_music_playing();
	//	if (saved_music)
	//		former_music_volume = saved_music->get_volume();
		stop_music();
	}

	// traverse the list of active sounds and process each
	for( iter = active_sounds.begin(); iter != active_sounds.end(); ++iter )
	{
		t_managed_sound * playing_sound = (*iter).get();
		if (minimized) // fade out the sound
		{
			playing_sound->set_former_volume(playing_sound->get_sound_ptr()->get_volume());
			playing_sound->get_sound_ptr()->set_volume(k_almost_min_volume);
		}
		else // restore prior volume level
		{
			if (playing_sound->get_sound_ptr())
			{
				playing_sound->get_sound_ptr()->set_volume(playing_sound->get_former_volume() );
			}
			else
			{
				minimized = minimized; // debug hook for a bug??
			}
		}
	}
}

// remove any sounds from object which died
void stop_owner_sound(const t_adventure_object  * owner)
{
	// go through the current managed sound list and make sure a sound
	// with this owner is NOT already playing
	t_active_sounds::iterator iter;

	// traverse the list of active sounds and process each
	for( iter = active_sounds.begin(); iter != active_sounds.end(); ++iter )
	{
		t_managed_sound * playing_sound = (*iter).get();

		if( &playing_sound->get_owner() == owner )
		{
			playing_sound->get_sound_ptr()->stop();
		}
	}
}

// ---------------------------------------------
// Play and adventure map sound and add it to
// the active playing sounds queue for update management
// NOTE: If the sound is not going to loop, you really
//       don't need it to be managed, just play it normally
//       and do not call this function for non-looping sounds
// ---------------------------------------------
t_managed_sound_ptr play_adventure_sound(	t_adventure_object const & owner,
											t_sound_ptr sound, 
											t_level_map_point_2d const & point, 
											int volume, bool fade_in )
{
	// go through the current managed sound list and make sure a sound
	// with this owner is NOT already playing
	t_active_sounds::iterator iter;

	// traverse the list of active sounds and process each
	for( iter = active_sounds.begin(); iter != active_sounds.end(); ++iter )
	{
		t_managed_sound * playing_sound = (*iter).get();

		if( &playing_sound->get_owner() == &owner )
		{
			return 0;
		}
	}

	t_playing_sound_ptr playing_sound = sound->play( volume, true, fade_in );

  	if( playing_sound && playing_sound->is_playing() )
	{
		t_managed_sound_ptr managed_sound = new t_managed_sound( owner, point, playing_sound );
		update_sound( point, managed_sound, false );
		active_sounds.push_back( managed_sound );
		return managed_sound;
	}

	return 0;
}

// ---------------------------------------------
// ---------------------------------------------
// ---------------------------------------------
// ---------------------------------------------
// ---------------------------------------------
// Active sound queue management
// ---------------------------------------------

int update_sound( t_level_map_point_2d point, t_managed_sound * playing_sound, bool initing_volume )
{
	t_level_map_point_2d const & sound_point = playing_sound->get_position();
	int dist_sq = 0;

	// if the sounds are on the same level
	// check their distances from one another
	if( point.level == sound_point.level && playing_sound->get_sound_ptr()->is_playing() )
	{
		int row_delta = point.row - sound_point.row;
		int col_delta = point.column - sound_point.column;

		dist_sq = ( row_delta * row_delta ) + ( col_delta * col_delta );

		// adjust the sound volume based on distance
		if( dist_sq <= k_max_sound_dist_sq )
		{
			int volume = get_sound_volume() + 10000;
			volume = (int)( (float)volume * ( 1.0f - ( (float)dist_sq / (float)k_max_sound_dist_sq ) ) ) - 10000;
			playing_sound->set_former_volume(volume);	// set intended volume
			playing_sound->get_sound_ptr()->set_volume( volume, !initing_volume );
		}
	}

	return dist_sq;
}

// ---------------------------------------------
// update the active sounds queue
// point is the current central point around
// which the sounds are checked
// ---------------------------------------------
void update_active_sounds( t_level_map_point_2d point, t_adventure_map const& map )
{
 	// look for near by adventure objects to play sounds for
	play_sounds_for_nearby_adventure_objects( point, map );

	t_active_sounds::iterator iter;

	// traverse the list of active sounds and process each
	for( iter = active_sounds.begin(); iter != active_sounds.end(); )
	{
		t_managed_sound * playing_sound = (*iter).get();

		if( playing_sound )
		{ 
			int dist_sq = update_sound( point, playing_sound );

			t_level_map_point_2d const & sound_point = playing_sound->get_position();

			// check for removal
			if( !playing_sound->get_sound_ptr()->is_playing() || 
				point.level != sound_point.level ||
				dist_sq > k_max_sound_dist_sq )
			{
				playing_sound->get_sound_ptr()->stop();
				iter = active_sounds.erase(iter);
				continue;
			} else 
				++iter;
		}
	}
}

// ---------------------------------------------
// close out the active sounds list
// ---------------------------------------------
void close_active_sounds()
{
	t_active_sounds::iterator iter;

	// traverse the list of active sounds and process each
	for( iter = active_sounds.begin(); iter != active_sounds.end(); )
	{
		t_managed_sound * playing_sound = (*iter).get();

		if( playing_sound )
		{
			playing_sound->get_sound_ptr()->fade_out();
			iter = active_sounds.erase(iter);
		}
	}
}




























